package gmmxEditor;

import static mh.entity.EnumState.BLASTED;
import static mh.entity.EnumState.BROKEN;
import static mh.entity.EnumState.BURNED;
import static mh.entity.EnumState.BURNING;
import static mh.entity.EnumState.CLOSED;
import static mh.entity.EnumState.DAMAGED;
import static mh.entity.EnumState.DUSTY;
import static mh.entity.EnumState.FLOATING;
import static mh.entity.EnumState.FROZEN;
import static mh.entity.EnumState.HIDDEN;
import static mh.entity.EnumState.INTACT;
import static mh.entity.EnumState.LOCKED;
import static mh.entity.EnumState.OPEN;
import static mh.entity.EnumState.WET;
import static mh.entity.EnumState.WRITTEN;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import mh.entity.object.EntityObject;
import mh.util.Base64;

/**
 * This class includes a static method that allows the user to convert from a matrix of int to the string for the .gmmx
 * file.
 * 
 * @author Gianmarco Laggia
 * 
 */
public class Converter {

	/**
	 * Encode the multidimensional array to a string. The array is gzipped and then base64 encoded.
	 * 
	 * @param matrix
	 *            int[width][height]
	 * @return the <code>String</code> encoded
	 */
	public static String encode(int[][] matrix) {
		ByteBuffer buffer = ByteBuffer.allocate((matrix[0].length * matrix.length * 4) + 4).order(ByteOrder.LITTLE_ENDIAN);
		buffer.putInt(555555);
		for (int i = 0; i < matrix[0].length; i++) {
			for (int j = 0; j < matrix.length; j++) {
				buffer.putInt(matrix[j][i]);
			}
		}
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		try {
			GZIPOutputStream stream = new GZIPOutputStream(out);
			stream.write(buffer.array());
			stream.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		byte[] gzippedByte = out.toByteArray();
		String outStr = Base64.encodeToString(gzippedByte, false);
		return outStr;
	}

	/**
	 * Encode the multidimensional array to a string. The array is gzipped and then base64 encoded.
	 * 
	 * @param matrix
	 *            int[width][height]
	 * @return the <code>String</code> encoded
	 */
	public static String encode(EntityObject[][] matrix) {
		ByteBuffer buffer = ByteBuffer.allocate((matrix[0].length * matrix.length * 7) + 4).order(ByteOrder.LITTLE_ENDIAN);
		int versionCode = 232323;
		buffer.putInt(versionCode);
		for (int i = 0; i < matrix[0].length; i++) {
			for (int j = 0; j < matrix.length; j++) {
				EntityObject o = matrix[j][i];
				if (o == null) {
					buffer.putShort((short) 0); //2 bytes ID
					//					buffer.put((byte) 0); // 1 byte HEALTH RATIO
					//					buffer.putInt(0); // 4 bytes FLAGS
				} else {
					buffer.putShort((short) o.getRender().getEntityDatabaseID());
					buffer.put((byte) Math.floor(o.getHealthRatio(255)));
					int toPut = 0;
					toPut |= toInt(o.is(INTACT)) << 0;
					toPut |= toInt(o.is(DAMAGED)) << 1;
					toPut |= toInt(o.is(BROKEN)) << 2;
					toPut |= toInt(o.is(BLASTED)) << 3;
					toPut |= toInt(o.is(BURNED)) << 4;
					toPut |= toInt(o.is(OPEN)) << 5;
					toPut |= toInt(o.is(CLOSED)) << 6;
					toPut |= toInt(o.is(LOCKED)) << 7;
					toPut |= toInt(o.is(BURNING)) << 8;
					toPut |= toInt(o.is(DUSTY)) << 9;
					toPut |= toInt(o.is(WET)) << 10;
					toPut |= toInt(o.is(FROZEN)) << 11;
					toPut |= toInt(o.is(WRITTEN)) << 12;
					toPut |= toInt(o.is(FLOATING)) << 13;
					toPut |= toInt(o.is(HIDDEN)) << 14;
					toPut |= toInt(o.isDamageable()) << 30;
					toPut |= toInt(o.isBlocked()) << 31;

					buffer.putInt(toPut);
				}
			}
		}
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		try {
			GZIPOutputStream stream = new GZIPOutputStream(out);
			stream.write(buffer.array());
			stream.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		byte[] gzippedByte = out.toByteArray();
		String outStr = Base64.encodeToString(gzippedByte, false);
		return outStr;
	}

	/**
	 * Encode the multidimensional array to a string. The array is gzipped and then base64 encoded.
	 * 
	 * @param encodedStr
	 *            a string that has been gzipped and base64 encoded.
	 * @param width
	 *            The width of the map, that is the width of the int[][]
	 * @param height
	 *            The height of the map, that is the height of the int[][]
	 * @return the linear array of int decoded.
	 * @throws IOException
	 */
	public static int[][] decode(String encodedStr, int width, int height) throws IOException {
		if ((width == 0) || (height == 0) || "".equals(encodedStr)) {
			throw new IllegalArgumentException();
		}
		char[] ch = encodedStr.trim().toCharArray();
		byte[] dec = Base64.decode(ch);
		ByteArrayInputStream bais = new ByteArrayInputStream(dec);
		int[][] ret = new int[width][height];
		GZIPInputStream is = new GZIPInputStream(bais);
		for (int y = 0; y < height; y++) {
			for (int x = 0; x < width; x++) {
				int objectId = 0;
				objectId |= is.read();
				objectId |= is.read() << 8;
				objectId |= is.read() << 16;
				objectId |= is.read() << 24;
				ret[x][y] = objectId;
			}
		}
		bais.close();
		return ret;

	}

	private static int toInt(boolean b) {
		if (b) {
			return 1;
		} else {
			return 0;
		}
	}
}
